#define _WIN32_WINNT        0x0400  // kharmon.  08-01-06.  Needed for ReadDirectoryChangesW

#include <windows.h>
#include <winsock2.h>
#include <process.h>

#include "lib.h"
#include "patch.h"
#include "patchsrv.h"
#include "portio.h"
#include "pkglist.h"
#include "client.h"
#include "rw2.h"
#include "admin.h"
#include "log.h"
#include "timer.h"
#include "opts.h"
#include "RcvPkgUpdate.h"

#define PATCH_SERVER_CONFIG    L"patchsrv.ini"

PatchServer* PatchServer::This = (PatchServer*)0;

#define SELF    PatchServer
#define SUPER    ServerInterface

PatchServer::PatchServer(int32 Port)
    : SUPER(Port)
{
    WSADATA wsaData;

    server = INVALID_SOCKET;
    stopEvt = sockEvt = (HANDLE)0;
    sockinit = false;
    stopping = 0;
    portio = (PortIO*)0;
    pkgs = (PackageList*)0;
    pkgRW = (ReadersWriters*)0;
    cliMutex = (PCRITICAL_SECTION)0;
    cliTransIn = (PCRITICAL_SECTION)0;
    cliTransOut = (PCRITICAL_SECTION)0;
    clients = (Client*)0;
    admin = (Admin*)0;
    timer = (Timer*)0;
    cleanClientsThread = (HANDLE)0;
    hmrtgThread = (HANDLE)0;
    numClients = 0;
    bytesIn = 0;
    bytesOut = 0;
    timestarted = time(0);

    // kharmon.  08-03-06.  Used for new package upload process.
    m_hNewPkgDir          = INVALID_HANDLE_VALUE;
    m_hPkgDirEvent        = NULL;
    m_hNewPkgTrigger      = NULL;
    m_hPackageWatchThread = NULL;
    m_pPkgOverlap         = NULL;

	// load config
	ServerOpts::load();

    // make sure that the required directories exist.
    CreateDirectory(ServerOpts::patchPackagesPath   , NULL);
    CreateDirectory(ServerOpts::patchNewPackagesPath, NULL);

	// CSN-14503 - Get packages list file
	String pkgList = ServerOpts::patchPackagesList;
	char* packages = new char[pkgList.getLength() + 1];
	StringConversions::convert((char*)packages, pkgList.getBuffer(0), pkgList.getLength());
	pkgList.releaseBuffer();
	packages[pkgList.getLength()] = '\0';

    // if the pkglist.ini doesn't exist, create it.
	Log::msgInformation("Checking package list file %S", (const uint16*)pkgList);
    if (GetFileAttributes(pkgList) == INVALID_FILE_ATTRIBUTES)
    {
		Log::msgError("Package list file wasn't found in %S", (const uint16*)pkgList);
        FILE *fp = fopen(packages, "wt");
        if(fp!=NULL)
        {
            fprintf(fp,"[Packages]\nnum=0\n");
            fclose(fp);
        }
    }

    // package list
    //
    pkgs = new PackageList();
    if (!pkgs)
        return;
    pkgs->load();
    pkgs->save();

    pkgRW = new ReadersWritersProb2();
    if (!pkgRW)
        return;

    // winsock
    //
    if (0 != WSAStartup(MAKEWORD(2,0), &wsaData))
    {
        Log::msgError("PatchServer:  WSAStartup failed (%ld)  ",
            WSAGetLastError());
        return;
    }
    if (LOBYTE(wsaData.wVersion) != 2 ||
        HIBYTE(wsaData.wVersion) != 0)
    {
        Log::msgError("PatchServer:  no valid WinSock 2.0 (%04x)  ",
            wsaData.wVersion);
        WSACleanup();
        return;
    }
    sockinit = true;

    stopEvt = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!stopEvt)
        return;

    cliMutex = new CRITICAL_SECTION;
    if (!cliMutex)
        return;
    ZeroMemory(cliMutex, sizeof(CRITICAL_SECTION));
    InitializeCriticalSection(cliMutex);

    cliTransIn = new CRITICAL_SECTION;
    if (!cliTransIn)
        return;
    ZeroMemory(cliTransIn, sizeof(CRITICAL_SECTION));
    InitializeCriticalSection(cliTransIn);

    cliTransOut = new CRITICAL_SECTION;
    if (!cliTransOut)
        return;
    ZeroMemory(cliTransOut, sizeof(CRITICAL_SECTION));
    InitializeCriticalSection(cliTransOut);

    timer = new Timer();
    if (!timer)
        return;

    if (!startListening())
    {
        Log::msgError("PatchServer:  startListening failed  ");
        return;
    }

    TCHAR inifile[MAX_PATH];

    int32 len = GetModuleFileName(NULL, inifile, MAX_PATH);
    while (len && inifile[len - 1] != '\\')
        len--;
    lstrcpy(inifile + len, PATCH_SERVER_CONFIG);
    
	SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);

	// [AO 2007-06-28] Limit number of worker threads to a value supported by WaitForMultipleObjects()
	int workersPerCPU = GetPrivateProfileInt(L"Options", L"NumWorkers", 8, inifile);
	int numWorkers = workersPerCPU * sysinfo.dwNumberOfProcessors;
	if (numWorkers > MAXIMUM_WAIT_OBJECTS)
		numWorkers = MAXIMUM_WAIT_OBJECTS;

	portio = new PortIO(numWorkers);

    admin = new Admin;

    This = this;

    cleanClientsThread = (HANDLE)_beginthreadex(
        (void*)0, 0, (unsigned(__stdcall*)(void*))cleanUpThread,
        (void*)this, 0, 0);
	//RPFX We found a overflow on this thread uint64 to int32
	//This thread writes some stats on a txt file and is not used 
    /*
		hmrtgThread = (HANDLE)_beginthreadex(
        (void*)0, 0, (unsigned(__stdcall*)(void*))mrtgThread,
        (void*)this, 0, 0);
	*/
    // kharmon.  08-03-06.  Added for new package upload process.
    createNewPackageWatch();

}

PatchServer::~PatchServer()
{
    Client* kill;

    stop();

    if (portio)
    {
        portio->stop();
        delete portio;
        portio = (PortIO*)0;
    }

    if (cliMutex)
    {
        DeleteCriticalSection(cliMutex);
        delete cliMutex;
        cliMutex = (PCRITICAL_SECTION)0;
    }

    if (cliTransIn)
    {
        DeleteCriticalSection(cliTransIn);
        delete cliTransIn;
        cliTransIn = (PCRITICAL_SECTION)0;
    }


    if (cliTransOut)
    {
        DeleteCriticalSection(cliTransOut);
        delete cliTransOut;
        cliTransOut = (PCRITICAL_SECTION)0;
    }

    while (clients)
    {
        kill = clients;
        clients = clients->getNext();
        delete kill;
    }
    
    // kharmon.  08-03-06.  Added for new package upload process.
    destroyNewPackageWatch();

    if (stopEvt != (HANDLE)0)
    {
        CloseHandle(stopEvt);
        stopEvt = (HANDLE)0;
    }

    if (sockinit)
    {
        WSACleanup();
        sockinit = false;
    }

    delete pkgRW;
    pkgRW = (ReadersWriters*)0;

    if (pkgs)
    {
        pkgs->save();
        delete pkgs;
        pkgs = (PackageList*)0;
    }

    delete admin;
    admin = (Admin*)0;

    delete timer;
    timer = (Timer*)0;

    This = (PatchServer*)0;
}


bool8 
PatchServer::initSuccess()
{
    char msg[256];

    sprintf(msg, "PatchServer::initSuccess: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", 
        server != INVALID_SOCKET,
        stopEvt,
        sockEvt,
        sockinit,
        portio,
        portio && portio->initOk(),
        pkgs,
        pkgRW,
        pkgRW && pkgRW->initOk(),
        cliMutex,
        cliTransIn,
        cliTransOut,
        timer,
        admin,
        admin && admin->initOk(),
        SUPER::initSuccess(),
        m_hNewPkgDir!=INVALID_HANDLE_VALUE,     // kharmon.  08-03-06.  Added for new package upload process.
        m_hPkgDirEvent,
        m_hNewPkgTrigger,
        m_hPackageWatchThread
        );

    Log::msgInformation(msg);


    return (bool8)(server != INVALID_SOCKET &&
        stopEvt && sockEvt  && sockinit && portio && portio->initOk() &&
        pkgs && pkgRW && pkgRW->initOk() &&
        cliMutex && cliTransIn && cliTransOut && timer &&
        admin && admin->initOk() && m_hPkgDirEvent!=NULL &&
        SUPER::initSuccess() && m_hNewPkgDir!=INVALID_HANDLE_VALUE &&
        m_hNewPkgTrigger!=NULL && m_hPackageWatchThread !=NULL);
}

// CSN-6738. Remove when investigation finishes
Client* PatchServer::NewClient(SOCKET c)
{
	return new Client((HANDLE) c);
}

void 
PatchServer::run()
{
    DWORD    result;
    HANDLE    events[4];
    SOCKET    client;
    Client* newClient;
    WSANETWORKEVENTS ne;
    bool8    isRunning = true;
    uint32    dropDeadTime;

    if (!initSuccess())
        return;

    events[0] = stopEvt;
    events[1] = sockEvt;
    events[2] = timer->GetHandle();
    events[3] = m_hPkgDirEvent;     // kharmon.  08-03-06.  This event fires when files are added to the "NewPackages" directory.

    // Begin watching the "NewPackage" directory for changes.
    startNewPackageWatch();
 
    // CSN-6738. Remove when investigation finishes
	__try
	{
		while (isRunning)
		{
			result = WaitForMultipleObjects(4, events, FALSE, INFINITE);

			switch (result)
			{
				case WAIT_OBJECT_0:        // stopEvt
				{
					isRunning = false;
					break;
				}
				case WAIT_OBJECT_0 + 1:    // new connection
				{					
					EnterCriticalSection(cliMutex);

					WSAEnumNetworkEvents(server, sockEvt, &ne);

					if (ne.lNetworkEvents & FD_ACCEPT)
					{
						client = accept(server, NULL, NULL);

						if (client != INVALID_SOCKET)
						{
							//newClient = new Client((HANDLE)client);	
							newClient = NewClient(client);	// CSN-6738. Remove when investigation finishes

							if (newClient)
							{
								if (newClient->initOk() &&
									portio->newPortUser(newClient->getNet()))
								{
									add(newClient);

									newClient->ready();
								}
								else
								{
									delete newClient;
									closesocket(client);
								}
							}
							else
							{
								closesocket(client);
							}
						}
						else
							Log::msgError("PatchServer::run:  accept() failed  ");
					}
					LeaveCriticalSection(cliMutex);
					break;
				}
				case WAIT_OBJECT_0 + 2:
				{
					timer->lock();

					{
						long val;
						void* who;

						while ((val=timer->TimerExpired(who)) != -1)
						{
							Client* client = (Client*)who;

							PostQueuedCompletionStatus(portio->getPort(), 0, KEY_TIMER,
								(LPOVERLAPPED)client->getNet()->getOver());
						}
						timer->TimerCompletion ();
					}

					timer->unlock();

					break;
				}
				case WAIT_OBJECT_0 + 3:
				{
					// kharmon.  08-01-06.  There was a change in the "New Packages" directory.
					// Begin watching for the next change.  The upload thread has been alerted and will handle
					// everything else.
					resetNewPackageWatch();
					break;
				}
				default:
				{
					Log::msgError("PatchServer::run:  wait failed (result: 0x%08x"
						"  error: %ld)  ", result, GetLastError());
					break;
				}
			}
		}
	}
	__except (CreateMiniDump( GetExceptionInformation()))
	{
		// CSN-6738. Remove when investigation finishes
		Log::msgError("SEH exception");
	}
	

    stopping = true;
    cleanUpClosedClients();
    // CSN-4006 - TECH: Improvements to Patch Server
    //Log::numUsers(numClients, ServerOpts::appName);
    dropDeadTime = 5 * 60 * 1000;    // 5 minutes for stale connections

    while (1)
    {
        Log::stoppingCheckpoint();
        // CSN-4006 - TECH: Improvements to Patch Server
        //Log::numUsers(numClients, ServerOpts::appName);

        cleanUpClosedClients();

        //
        // todo: don't quit while admin is active.  This requires
        // splitting the admin thread into a listener thread and an io thread.
        // Stopping the server, would then just stop the listener thread
        // wait for the io thread to quit.
        //

        if (numClients <= 0)
            break;

        if (allStale(dropDeadTime))
            break;

        Sleep(5000);
    }

    portio->stop();
    admin->stop();
}

bool8
PatchServer::addTimer(long milisecs, Client* client)
{
    bool8 result = false;

    timer->lock();

    result = (timer->AddDelta(milisecs, client) != -1);

    timer->unlock();

    return result;
}


void 
PatchServer::stop()
{
    if (cleanClientsThread)
    {
        TerminateThread(cleanClientsThread, 0);
        cleanClientsThread = 0;
    }

    if (hmrtgThread)
    {
        TerminateThread(hmrtgThread, 0);
        hmrtgThread = 0;
    }
    
    stopNewPackageWatch();  // kharmon.  08-03-06.  Added for new package upload process.

    EnterCriticalSection(cliMutex);

    SetEvent(stopEvt);
    stopListening();
    if (admin)
        admin->shutdown();

    LeaveCriticalSection(cliMutex);
}

void
PatchServer::add(Client* toAdd)
{
    ASSERT(toAdd != (Client*)0);

    EnterCriticalSection(cliMutex);

    toAdd->setNext(clients);
    toAdd->setPrev((Client*)0);

    if (clients)
        clients->setPrev(toAdd);

    clients = toAdd;
    ++numClients;
    // CSN-4006 - TECH: Improvements to Patch Server
    //Log::numUsers(numClients, ServerOpts::appName);

    LeaveCriticalSection(cliMutex);
}

void 
PatchServer::close(Client* kill)
{
    ASSERT(kill != (Client*)0);

    timer->lock();
    timer->Remove(kill);
    timer->unlock();

    EnterCriticalSection(cliMutex);

    if (kill->getNext())
        kill->getNext()->setPrev(kill->getPrev());

    if (kill->getPrev())
        kill->getPrev()->setNext(kill->getNext());
    else
        clients = kill->getNext();

    delete kill;
    --numClients;
    //RPF 20090723 ALWAYS REPORT THE NUM OF USERS CONNECTED
    //if (stopping)
    // CSN-4006 - TECH: Improvements to Patch Server
    //Log::numUsers(numClients, ServerOpts::appName);

    LeaveCriticalSection(cliMutex);
}

bool8
PatchServer::startListening()
{
    SOCKADDR_IN localAddr;
    INT err;

    server = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (server == INVALID_SOCKET)
    {
        Log::msgError("PatchServer::startListening:  server socket create "
            "failed (%ld)  ", WSAGetLastError());
        return false;
    }

    ZeroMemory(&localAddr, sizeof(SOCKADDR_IN));
    localAddr.sin_port = htons((u_short)port);
    localAddr.sin_family = AF_INET;

    err = bind(server, (PSOCKADDR)&localAddr, sizeof(SOCKADDR_IN));
    if (err == SOCKET_ERROR)
    {
        Log::msgError("PatchServer::startListening:  server socket bind "
            "failed (%ld)  ", WSAGetLastError());
        closesocket(server);
        server = INVALID_SOCKET;
        return false;
    }
    
    err = listen(server, ServerOpts::nNumListeners);    // kharmon.  12-01-05.  #7665.  Adding option to control the size of the socket listener queue.
    if (err == SOCKET_ERROR)
    {
        Log::msgError("PatchServer::startListening:  server listen"
            " failed (%ld)  ", WSAGetLastError());
        closesocket(server);
        server = INVALID_SOCKET;
        return false;
    }

    sockEvt = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!sockEvt)
    {
        Log::msgError("PatchServer::startListening:  sockEvt CreateEvent "
            "failed (%ld)  ", WSAGetLastError());
        closesocket(server);
        server = INVALID_SOCKET;
        return false;
    }

    if (SOCKET_ERROR == WSAEventSelect(server, sockEvt, FD_ACCEPT))
    {
        Log::msgError("PatchServer::startListening:  WSAEventSelect"
            " failed (%ld)  ", WSAGetLastError());
        CloseHandle(sockEvt);
        sockEvt = (HANDLE)0;
        closesocket(server);
        server = INVALID_SOCKET;
        return false;
    }

    return true;
}

void
PatchServer::stopListening()
{
    if (sockEvt)
    {
        if (server)
            WSAEventSelect(server, sockEvt, 0);

        CloseHandle(sockEvt);
        sockEvt = (HANDLE)0;
    }
    if (server != INVALID_SOCKET)
    {
        closesocket(server);
        server = INVALID_SOCKET;
    }
}


PackageList*    
PatchServer::getPackageList()
{
    if (pkgRW->beginRead())
        return pkgs;
    return (PackageList*)0;
}

void            
PatchServer::releasePackageList()
{
    pkgRW->endRead();
}

PackageList*    
PatchServer::lockPackageList()
{
    if (pkgRW->beginWrite())
        return pkgs;
    return (PackageList*)0;
}

void            
PatchServer::unlockPackageList()
{
    pkgRW->endWrite();
}

uint32
PatchServer::getPkgListChangeCount()
{
    return pkgRW->getChangeCount();
}

bool8
PatchServer::allStale(uint32 dropDeadTime)
{
    bool8 result = true;
    Client* probe;

    EnterCriticalSection(cliMutex);

    probe = clients;
    while (result && probe)
    {
        result = probe->isStale(dropDeadTime);
        probe = probe->getNext();
    }

    LeaveCriticalSection(cliMutex);

    return result;
}

void PatchServer::cleanUpClosedClients()
{
    Client* pCurrentClient = clients;
    Client* pNextClient = NULL;

    while (pCurrentClient)
    {
        pNextClient = pCurrentClient->getNext();
        if (pCurrentClient->isClosed())
        {
            close(pCurrentClient);
        }
        // luke 4/1/02: Close a connection if it is appears to be stale
        else if (pCurrentClient->isStale(STALE_TIMEOUT))
        {
            // Note that calling PatchServer::close here rather than Client::close
            // will trigger a debug assertion in the PortUserOverlapped destructor 
            // (a posted recv operation is still pending on the socket).
            // If the connection is closed now, on the other hand, the read will 
            // gracefully fail and pCurrentClient will be ready for deletion.

            timer->lock();
            timer->Remove(pCurrentClient);
            timer->unlock();
            
            pCurrentClient->netLock();            
            pCurrentClient->close();
            pCurrentClient->netUnlock();
        }

        pCurrentClient = pNextClient;
    }
}


bool8
PatchServer::isCorrectRemoteUpdateHost(const String& remoteip)
{
    // always allow update from localhost
    if  ( ( remoteip == "127.0.0.1" ) || ( remoteip == "localhost" ) )
    {
        return true;
    }

    // always allow update from same host computer
    char localhostbuf[255];
    if (gethostname(localhostbuf, sizeof(localhostbuf)) != SOCKET_ERROR) 
    {
        hostent* localhost = gethostbyname(localhostbuf);
        if ( localhost )
        {
            for(int32 j=0; localhost->h_addr_list[j] != 0; ++j)
            {
                IN_ADDR in;
                in.s_addr = ((uint32*)*localhost->h_addr_list)[j];
            
                char* localip = inet_ntoa(in);

                if (remoteip == localip)
                    return true;
            }
        }
    }

    TCHAR hostbuf[255];
    TCHAR inifile[MAX_PATH];

    int32 len = GetModuleFileName(NULL, inifile, MAX_PATH);
    while (len && inifile[len - 1] != '\\')
        len--;
    lstrcpy(inifile + len, PATCH_SERVER_CONFIG);

    GetPrivateProfileString(L"Updates", L"remotehost", L"", hostbuf, 255, inifile);

    if (hostbuf[0] == '\0')
        return false;

    TCHAR* dotstar = wcsstr(hostbuf, L"*");
    if (dotstar)
    {
        *dotstar = '\0';
        int32 len;
        bool8 val;

        len = wcslen(hostbuf);
        val = remoteip.caseCompare(0, len, hostbuf) == 0;
        return val;
    }

    char tmp[255+1];
    StringConversions::convert(tmp, hostbuf, 255+1);
    hostent* host = gethostbyname(tmp);
    
    if (!host)
        return false;

    for(int32 i=0; host->h_addr_list[i] != 0; i++)
    {
        IN_ADDR in;
        in.s_addr = ((uint32*)*host->h_addr_list)[i];
    
        char* correctip = inet_ntoa(in);

        if (remoteip == correctip)
            return true;
    }
    
    return false;
}

bool8
PatchServer::isCorrectLogin(const String& username, const String& password)
{
    TCHAR correctpassword[255];
    TCHAR inifile[MAX_PATH];

    int32 len = GetModuleFileName(NULL, inifile, MAX_PATH);
    while (len && inifile[len - 1] != '\\')
        len--;
    lstrcpy(inifile + len, PATCH_SERVER_CONFIG);

    GetPrivateProfileString(L"Users", username, L"", correctpassword, 255, inifile);

    if (correctpassword[0] == '\0')
        return false;

//    if (strcmp(password, correctpassword) == 0)
    if (password == correctpassword)
        return true;

    return false;
}

uint32 __stdcall
PatchServer::cleanUpThread(PatchServer* This)
{
    TCHAR inifile[MAX_PATH];
    TCHAR seconds[128+1];
    DWORD result;

    int32 len = GetModuleFileName(NULL, inifile, MAX_PATH);
    while (len && inifile[len - 1] != '\\')
        len--;
    lstrcpy(inifile + len, PATCH_SERVER_CONFIG);

    GetPrivateProfileString(L"Config", L"cleanwait", L"120", seconds, 128, inifile);

    uint32 cleanwait = MIN(10, _wtol(seconds));    //no less than 10 seconds

    while (1)
    {
        result = WaitForSingleObject(This->stopEvt, 1000*cleanwait);    //clean up every X seconds from config

        if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED)    // quit
            break;

        This->cleanUpClosedClients();
    }

    _endthreadex(0);
    return 0;
}

uint32 __stdcall
PatchServer::mrtgThread(PatchServer* This)
{
    DWORD result;
    char uptime[256];

    while (1)
    {
        result = WaitForSingleObject(This->stopEvt, 1000*60);        //every minute write out the stats

        if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED)    // quit
            break;

        uint32 now = time(0);
        
        uint32 delta = now - This->timestarted;

        sprintf(uptime, "%d seconds", delta);

        FILE* stats = fopen("patchstats.txt", "w");
        
        EnterCriticalSection(This->cliTransIn);
        fprintf(stats, "%d\n", This->bytesIn);
        LeaveCriticalSection(This->cliTransIn);

        EnterCriticalSection(This->cliTransOut);
        fprintf(stats, "%d\n", This->bytesOut);
        LeaveCriticalSection(This->cliTransOut);
        
        fprintf(stats, "%s\n", uptime);

        wchar_t * CN = ServerOpts::casinoName.getBuffer(0);
        fprintf(stats, "%S Patch Server\n", CN);
        ServerOpts::casinoName.releaseBuffer();

        fclose(stats);
    }

    _endthreadex(0);
    return 0;
}

void
PatchServer::addBytesIn(uint32 bytes)
{
    EnterCriticalSection(cliTransIn);

    bytesIn += bytes;

    LeaveCriticalSection(cliTransIn);
}

void
PatchServer::addBytesOut(uint32 bytes)
{
    EnterCriticalSection(cliTransOut);

    bytesOut += bytes;

    LeaveCriticalSection(cliTransOut);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn PatchServer::createNewPackageWatch()
///
/// This method opens all of the resources needed for the directory based package upload functionality
/// and also starts the watch thread.  The "watching" won't actually begin until "startNewPackageWatch"
/// is called though.
///
/// kharmon.  08-03-06.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void PatchServer::createNewPackageWatch()
{
    // Get a handle to the directory that we will watch looking for new package files.
	m_hNewPkgDir = CreateFile(ServerOpts::patchNewPackagesPath, 
                              FILE_LIST_DIRECTORY,                
                              FILE_SHARE_READ | FILE_SHARE_DELETE,  
                              NULL,                               
                              OPEN_EXISTING,                      
                              FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,        
                              NULL);

    // this event will fire whenever a file is added to the "NewPackages" directory, so that the main thread
    // can restart the "watch".
    m_hPkgDirEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    
    // This completion port is used to tell the "watch thread" that a new file has appeared, AND also to tell it
    // when to shutdown.
    m_hNewPkgTrigger = ::CreateIoCompletionPort(m_hNewPkgDir, NULL, NEW_PKG_FILE, 1);

    // this is the watch thread...let's start it up, though it won't actually begin watching until we call ReadDirectoryChangesW
    m_hPackageWatchThread = (HANDLE)_beginthreadex( (void*)0, 0, (unsigned(__stdcall*)(void*))packageWatchThread,
                                                    (void*)this, 0, 0);

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn PatchServer::startNewPackageWatch()
///
/// Called to begin actively watching on the "NewPackage" directory.
///
/// kharmon.  08-03-06.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void PatchServer::startNewPackageWatch()
{
    PostQueuedCompletionStatus(m_hNewPkgTrigger, 0, NEW_PKG_STARTUP, NULL);
    resetNewPackageWatch();
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn PatchServer::stopNewPackageWatch()
///
/// Tells the "watch thread" to shut down, and then waits for it to do so.
///
/// kharmon.  08-03-06.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void PatchServer::stopNewPackageWatch()
{
    PostQueuedCompletionStatus(m_hNewPkgTrigger, 0, NEW_PKG_SHUTDOWN, NULL);

    if(m_hPackageWatchThread!=NULL)
    {        
        WaitForSingleObject( m_hPackageWatchThread, INFINITE );

        CloseHandle( m_hPackageWatchThread );
        m_hPackageWatchThread = NULL;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn  PatchServer::destroyNewPackageWatch()
///
/// Frees the handles associated with the new package upload process
///
/// kharmon.  08-03-06.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void PatchServer::destroyNewPackageWatch()
{
    if(m_hNewPkgDir!=INVALID_HANDLE_VALUE)
    {        
        CloseHandle(m_hNewPkgDir);
        m_hNewPkgDir = NULL;
    }

    if(m_hPkgDirEvent!=NULL)
    {
        CloseHandle(m_hPkgDirEvent);
        m_hPkgDirEvent = NULL;
    }

    if(m_hNewPkgTrigger!=NULL)
    {
        CloseHandle(m_hNewPkgTrigger);
        m_hNewPkgTrigger = NULL;
    }

    if(m_pPkgOverlap!=NULL)
    {
        uint8 *pucReadInfo = (uint8 *) m_pPkgOverlap->Offset;
        delete [] pucReadInfo;
        delete m_pPkgOverlap;   
        m_pPkgOverlap      = NULL;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn  PatchServer::resetNewPackageWatch()
///
/// Everytime the asynchronous ReadDirectoryChangesW method fires, we need to reset it so that it will
/// keep watching the directory for the next set of changes.  So that's what this method does.  
///
/// kharmon.  08-03-06.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void PatchServer::resetNewPackageWatch()
{
    DWORD dwBytesReturned = 0;

    m_pPkgOverlap    = new OVERLAPPED;
    uint8 *pReadInfo = new uint8[FILE_NAME_INFO_BUFFER_SIZE];

    memset(m_pPkgOverlap, 0, sizeof(OVERLAPPED));
    memset(pReadInfo, 0, FILE_NAME_INFO_BUFFER_SIZE);

    m_pPkgOverlap->hEvent = m_hPkgDirEvent; 
    m_pPkgOverlap->Offset = (DWORD) pReadInfo;
    
    ReadDirectoryChangesW(m_hNewPkgDir,
                          pReadInfo,
                          FILE_NAME_INFO_BUFFER_SIZE,
                          FALSE,
                          FILE_NOTIFY_CHANGE_FILE_NAME,
                          &dwBytesReturned,
                          m_pPkgOverlap,
                          NULL);

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn  PatchServer::packageWatchThread(PatchServer* This)
///
/// This code is run on it's own thread to handle the process of checking for new packages in the 
/// "NewPackage" directory.  If any new packages are found and the "trigger" file is there, then we load
/// the packages in on this thread as well.
///
/// kharmon.  08-03-06.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
uint32 __stdcall PatchServer::packageWatchThread(PatchServer* This)
{
    DWORD dwKey             = NEW_PKG_STARTUP;
    DWORD dwBytesTransferred = 0;
    OVERLAPPED *pOverlapped = NULL;        

    while (dwKey!=NEW_PKG_SHUTDOWN) 
    {
        // this will block until either there's a file change OR until the
        // main thread posts a start or stop message to the queue.
        BOOL bSuccess = GetQueuedCompletionStatus(This->m_hNewPkgTrigger,
                                                  &dwBytesTransferred,
                                                  &dwKey,
                                                  &pOverlapped,
                                                  INFINITE);

        if(bSuccess==false)
        {            
            Log::msgError("Exiting New Package Watch thread.  Reason:  %d", (int) GetLastError());
            break;
        }

        switch(dwKey)
        {
            case NEW_PKG_STARTUP:
            {            
                DEBUGMSG(("\nSTARTUP"));
                // doesn't really do anything currently.
                break;
            }

            case NEW_PKG_FILE:
            {
                DEBUGMSG(("\nNEW FILE"));
                // if we get this message, then a file was created, deleted, or renamed in
                // the "NewPackages" directory.  Every time this happens, we check to see
                // if there's a trigger file...if there is, we process all of the packages
                // that are in the directory at that time.  The "trigger" file should always
                // be added to the directory after all of the other files are in place.  The
                // arrival of the trigger file triggers the start of the upload process.

                // free this memory.
                uint8 *pucReadInfo = (uint8 *) pOverlapped->Offset;                
                delete [] pucReadInfo;
                delete pOverlapped;

                // now check to see if there's anything to upload.
                RecievePackageUpdate::checkUploadDirectory();

                break;
            }

            case NEW_PKG_SHUTDOWN:
            {                
                DEBUGMSG(("\nSHUTDOWN\n"));
                // there's nothing to do, but this message will now cause 
                // us to exit the while loop.
                break;
            }
        }
    }

    _endthreadex(0);
    return 0;
}

// CSN-6738. Remove when investigation finishes
int PatchServer::CreateMiniDump( EXCEPTION_POINTERS* pep ) 
{
  HANDLE hFile = CreateFile( L"./PatchDump.dmp", GENERIC_READ | GENERIC_WRITE, 
    0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); 

  if( ( hFile != NULL ) && ( hFile != INVALID_HANDLE_VALUE ) ) 
  {
    // Create the minidump 

    MINIDUMP_EXCEPTION_INFORMATION mdei; 

    mdei.ThreadId           = GetCurrentThreadId(); 
    mdei.ExceptionPointers  = pep; 
    mdei.ClientPointers     = FALSE; 

    MINIDUMP_TYPE mdt       = MiniDumpNormal; 

    BOOL rv = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), 
      hFile, mdt, (pep != 0) ? &mdei : 0, 0, 0 ); 

    if( !rv ) 
	{
		Log::msgError("MiniDumpWriteDump failed. Error: %u \n", GetLastError());
	}
    else 
	{
		Log::msgError("Minidump created");
	}

    // Close the file 
    CloseHandle( hFile ); 
  }
  else
  {
	  Log::msgError("MiniDumpWriteDump failed. Error: %u \n", GetLastError());
  }

  return EXCEPTION_EXECUTE_HANDLER;
}